﻿using System;
using System.Reflection.Emit;

namespace Aeon.Emulator.Decoding.Emitters
{
    /// <summary>
    /// Emits IL for a decoding operation.
    /// </summary>
    internal abstract class Emitter
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the Emitter class.
        /// </summary>
        /// <param name="state">Common emitter state information.</param>
        protected Emitter(EmitStateInfo state)
        {
            this.il = state.IL;
            this.state = state;
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets the type of local variable needed to store the operand value as a method argument.
        /// </summary>
        public abstract Type MethodArgType { get; }
        /// <summary>
        /// Gets a value indicating whether a temporary write-back variable is or may be required.
        /// </summary>
        public virtual bool? RequiresTemp
        {
            get { return false; }
        }
        /// <summary>
        /// Gets the type of local variable required to store temporary write-back values.
        /// </summary>
        public virtual Type TempType
        {
            get { return null; }
        }
        /// <summary>
        /// Gets the local variable which contains the parameter is address boolean value if applicable.
        /// </summary>
        public virtual LocalBuilder ParamIsAddressLocal
        {
            get { return null; }
        }
        #endregion

        #region Protected Properties
        /// <summary>
        /// Gets the current processor word size.
        /// </summary>
        protected int WordSize
        {
            get { return this.state.WordSize; }
        }
        /// <summary>
        /// Gets a value indicating whether the value should be returned by value or by address.
        /// </summary>
        protected EmitReturnType ReturnType
        {
            get { return this.state.ReturnType; }
        }
        /// <summary>
        /// Gets the current addressing mode; this will be either 16 or 32.
        /// </summary>
        protected int AddressMode
        {
            get { return this.state.AddressMode; }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Emits IL for the operation.
        /// </summary>
        public abstract void EmitLoad();
        /// <summary>
        /// Emits IL for after the emulate method is called.
        /// </summary>
        public virtual void EmitEpilog()
        {
        }
        #endregion

        #region Protected Methods
        /// <summary>
        /// Loads the vm.Processor.CachedIP pointer onto the stack.
        /// </summary>
        protected void LoadIPPointer()
        {
            LoadProcessor();
            il.Emit(OpCodes.Ldfld, Infos.Processor.CachedIP);
        }
        /// <summary>
        /// Loads the vm.Processor reference onto the stack.
        /// </summary>
        protected void LoadProcessor()
        {
            il.LoadLocal(this.state.ProcessorLocal);
        }
        /// <summary>
        /// Loads the vm.PhysicalMemory reference onto the stack.
        /// </summary>
        protected void LoadPhysicalMemory()
        {
            il.LoadArgument(VmArgument);
            il.Emit(OpCodes.Ldfld, Infos.VirtualMachine.PhysicalMemory);
        }
        /// <summary>
        /// Emits code for calling a GetTypeValue method on PhysicalMemory.
        /// </summary>
        /// <param name="size">Size of integer to get in bytes.</param>
        protected void CallGetMemoryInt(int size)
        {
            switch(size)
            {
            case 1:
                il.Emit(OpCodes.Call, Infos.PhysicalMemory.GetByte);
                break;

            case 2:
                il.Emit(OpCodes.Call, Infos.PhysicalMemory.GetUInt16);
                break;

            case 4:
                il.Emit(OpCodes.Call, Infos.PhysicalMemory.GetUInt32);
                break;

            case 8:
                il.Emit(OpCodes.Call, Infos.PhysicalMemory.GetUInt64);
                break;

            default:
                throw new ArgumentException("Unsupported type.", "type");
            }
        }
        /// <summary>
        /// Emits code for calling a GetTypeValue method on Physical memory.
        /// </summary>
        /// <param name="size">Size of floating-point value to get in bytes.</param>
        protected void CallGetMemoryReal(int size)
        {
            switch(size)
            {
            case 4:
                il.Emit(OpCodes.Call, Infos.PhysicalMemory.GetReal32);
                break;

            case 8:
                il.Emit(OpCodes.Call, Infos.PhysicalMemory.GetReal64);
                break;

            case 10:
                il.Emit(OpCodes.Call, Infos.PhysicalMemory.GetReal80);
                break;

            default:
                throw new ArgumentException("Unsupported type.", "type");
            }
        }
        /// <summary>
        /// Increments both IP/EIP local pointer variable and the Processor.IP/EIP register by a specified amount.
        /// </summary>
        /// <param name="n">Value to add to IP/EIP.</param>
        protected void IncrementIPPointer(int n)
        {
            LoadProcessor();
            il.Emit(OpCodes.Dup);
            il.Emit(OpCodes.Ldfld, Infos.Processor.CachedIP);
            il.LoadConstant(n);
            il.Emit(OpCodes.Conv_I);
            il.Emit(OpCodes.Add);
            il.Emit(OpCodes.Stfld, Infos.Processor.CachedIP);

            //il.LoadLocal(ipPointer);
            //il.LoadConstant(n);
            //il.Emit(OpCodes.Add);
            //il.StoreLocal(ipPointer);

            //LoadProcessor();
            //il.Emit(OpCodes.Ldfld, Infos.Processor.PIP);
            //il.Emit(OpCodes.Dup);
            //il.Emit(OpCodes.Ldind_U4);
            //il.LoadConstant(n);
            //il.Emit(OpCodes.Add);
            //il.Emit(OpCodes.Stind_I4);
        }
        /// <summary>
        /// Returns the type needed to store an unsigned integer of the specified size.
        /// </summary>
        /// <param name="size">Size of integer in bytes.</param>
        /// <returns>Type needed to store the integer.</returns>
        protected static Type GetUnsignedIntType(int size)
        {
            switch(size)
            {
            case 1:
                return typeof(byte);

            case 2:
                return typeof(ushort);

            case 4:
                return typeof(uint);

            case 6:
            case 8:
                return typeof(ulong);

            default:
                throw new ArgumentException("Invalid size.");
            }
        }
        /// <summary>
        /// Returns the type needed to store a signed integer of the specified size.
        /// </summary>
        /// <param name="size">Size of integer in bytes.</param>
        /// <returns>Type needed to store the integer.</returns>
        protected static Type GetSignedIntType(int size)
        {
            switch(size)
            {
            case 1:
                return typeof(sbyte);

            case 2:
                return typeof(short);

            case 4:
                return typeof(int);

            case 6:
            case 8:
                return typeof(long);

            default:
                throw new ArgumentException("Invalid size.");
            }
        }
        /// <summary>
        /// Returns the type needed to store a floating-point value of the specified size.
        /// </summary>
        /// <param name="size">Size of the floating-point value in bytes.</param>
        /// <returns>Type needed to store the floating-point value.</returns>
        protected static Type GetFloatType(int size)
        {
            switch(size)
            {
            case 4:
                return typeof(float);

            case 8:
                return typeof(double);

            case 10:
                return typeof(Real10);

            default:
                throw new ArgumentException("Invalid size.");
            }
        }
        #endregion

        #region Private Fields
        /// <summary>
        /// Common emitter state information.
        /// </summary>
        private readonly EmitStateInfo state;
        #endregion

        #region Protected Fields
        /// <summary>
        /// Current ILGenerator instance.
        /// </summary>
        protected readonly ILGenerator il;
        #endregion

        #region Protected Constants
        /// <summary>
        /// Index of the VirtualMachine instance parameter.
        /// </summary>
        protected const int VmArgument = 0;
        #endregion
    }
}
